package edu.upenn.seas.peerreview.smui
import scala.collection.immutable._

/**
 * An implementation that fulfills the BattleshipLogic trait. See method
 * documentation in trait description.
 * 
 * @see BattleshipCommon.scala
 */
class BattleshipLogicImpl extends BattleshipLogic {
  def debug(msg: String) = Debug.debug("appmessage")("[RefLogic] " + msg)
  var lastHitId: Id = null // this is totally a hack

  var numHits = 0
  var numSunks = 0
  var numKills = 0
  
  var allShips = Set[Ship]()
  var refShips: Set[Ship] = null //updated every turn to reflect allShips
  
  // A queue of (attacker, targetPoint, result)
  val fireResultQ = new scala.collection.mutable.Queue[(Id, Point, FireResultType)]()

  var cfgs: NewGameConfigs = null
  var myId: Id = null
  var uiId: Id = null
  var myManagerId: Id = null
  var defeated = false

  def isGameLoopRunning: Boolean = !defeated
  
  private def getShipAt(p:Point, shipList : Set[Ship]) : Option[Ship] = { 
    val hitShips = (shipList.filter(s=>(s.points contains p))).toList
    if(hitShips.isEmpty) None
    else Some(hitShips(0))
  }
  
  private def getShipAtSure(p:Point, shipList : Set[Ship]) : Ship = {
    getShipAt(p, shipList) match {
      case Some(x) => x
      case _ => throw new RuntimeException("Was expecting a ship at " + p)
    }
  }
  
  /**
   * Figures out whether there was a hit or not and returns the resulting 
   * shipList. Note that shipLists are now immutable.
   */
  protected def firingAt(p:Point, shipList : Set[Ship]) : (FireResultType, Set[Ship]) = {    
    // Get all potential ships at this spot.
    getShipAt(p, shipList) match {
      case None => (Miss, shipList)
      case Some(hitShip) => {
        if (hitShip.links(p) == false) (RedundantHit, shipList) //already hit that link
        else {
          // Unhit spot: mark as a hit.
          val newShip = hitShip.shootAt(p)  
          val newShipList = shipList - hitShip + newShip //place in the new ship
          if (!newShip.sunk) 
            (Hit, newShipList) // There >1 unhit spot left.
          else {
            if(!newShipList.forall(_.sunk)) (Sunk, newShipList) 
            else (Defeat, newShipList)
          }
        }
      }
    }
  }
  
  def onInit(c: NewGameConfigs, myId: Id, uiId: Id, myManagerId: Id) {
    cfgs = c
    this.myId = myId
    this.uiId = uiId
    this.myManagerId = myManagerId
    defeated = false
    lastHitId = null
    numHits = 0
    numSunks = 0
    numKills = 0
    allShips = Set()
  }

  def onPlaceShip(bow: Point, stern: Point): Boolean = {
    allShips = allShips + new Ship(bow,stern)
    refShips = allShips
    (cfgs.ships.size == allShips.size)
  }

  def onFireAtMe(attacker: Id, targetPoint: Point) {
    lastHitId = attacker // HACK

    //figure out the fire-result from turn start
    val status = firingAt(targetPoint, refShips)._1

    //update 'real situation'
    allShips = firingAt(targetPoint, allShips)._2
    
    //possibly: admit defeat
    fireResultQ += (attacker, targetPoint, status)
  }

  def onHandleAttacks(sendFireResult: (Id, (Id, Point, FireResultType)) => Unit,
    sendPlayerResult: (Id, (Int, Int, Int, Boolean)) => Unit) {
   
      //first figure out if you've lost
      if(allShips.forall(_.sunk)) {
        defeated = true //i immediately stop responding.
        val playerresult = (numHits, numSunks, numKills, false)
        for (opp <- myId :: myManagerId :: cfgs.opponents)
           sendPlayerResult(opp, playerresult)
      }
      
      debug("before allhandle attacks, refShips look like " + refShips)
      for (fireresult <- fireResultQ) {
        fireresult match {
          case (attacker: Id, targetPoint: Point, result: FireResultType) => {
            //upgrade hit to sink if ship being fired at is sunk
            val withSink : FireResultType = 
              if(result == Hit && getShipAtSure(targetPoint, allShips).sunk) Sunk
              else result
            //upgrade hit/sink to defeat if there's a kill this round
            val withDefeat : FireResultType =
              if(List(Hit, Sink).contains(result) && allShips.forall(_.sunk)) Defeat
              else withSink
            for (opp <- (myId :: cfgs.opponents) ::: List(uiId))
              sendFireResult(opp, (attacker, targetPoint, withDefeat))
          }
          case s => None
        }
      }
    fireResultQ.clear()
    //set the reference point to the end of this turn.
    refShips = allShips
    debug("after allhandle attacks, ships look like " + allShips)
  }

  def onFireResult(attacker: Id, attackee: Id, targetPoint: Point, result: FireResultType) {
    if (attacker == myId) {
      result match {
        case Hit => numHits += 1
        case Sunk => numHits += 1; numSunks += 1
        case Defeat => numHits += 1; numSunks += 1; numKills += 1
        case _ =>
      }
    }
  }

  def onLastAlive(sendPlayerResult: (Id, (Int, Int, Int, Boolean)) => Unit) {
    val playerresult =  (numHits, numSunks, numKills, true)
    for(opp <- myId :: myManagerId :: cfgs.opponents)
      sendPlayerResult(opp, playerresult)
  }

  def onGameOver(sendPRMessage: Message => Unit) {
    sendPRMessage(OKToReleaseLog())
    val opponent = if (lastHitId != null) lastHitId else cfgs.opponents.head
    sendPRMessage(PleaseAudit(opponent))
  }
    
  def onTournamentOver(sendPRMessage: Message => Unit) {
// Since only one-game tournaments for now, just audit at game over.
//    sendPRMessage(OKToReleaseLog())
//    val opponent = if (lastHitId != null) lastHitId else cfgs.opponents.head
//    sendPRMessage(PleaseAudit(opponent))
  } 
}
